package com.asen.libcommon.http.livedata

import com.orhanobut.logger.Logger
import retrofit2.Response
import java.util.regex.Pattern

/**
 * @date   : 2021/3/1
 * @author : asenLiang
 * @e-mail : liangAisiSen@163.com
 * @desc   : liveData返回数据成功错误封装
 */
@Suppress("unused") // T is used in extending classes
sealed class LiveDataResponse<T> {
    companion object {
        fun <T> create(error: Throwable): ApiErrorResponse<T> {
            return ApiErrorResponse(error.message
                    ?: "unknown error", error.hashCode(), error.cause?.message)
        }

        fun <T> create(response: Response<T>): LiveDataResponse<T> {
            return if (response.isSuccessful) {
                val body = response.body()
                if (body == null || response.code() == 204) {
                    ApiEmptyResponse()
                } else {
                    ApiSuccessResponse(body = body, linkHeader = response.headers().get("link"))
                }
            } else {
                val msg = "${response.errorBody()?.string()}"
                val errorMsg = if (msg.isNullOrEmpty()) response.message() else msg
                ApiErrorResponse(errorMsg ?: "unknown error", errorMsg.hashCode(), errorMsg)
            }
        }
    }
}

/**
 * separate class for HTTP 204 responses so that we can make ApiSuccessResponse's body non-null.
 */
class ApiEmptyResponse<T> : LiveDataResponse<T>()

data class ApiSuccessResponse<T>(
        val body: T,
        val links: Map<String, String>
) : LiveDataResponse<T>() {

    constructor(body: T, linkHeader: String?) : this(
            body = body,
            links = linkHeader?.extractLinks() ?: emptyMap()
    )

    val nextPage: Int? by lazy(LazyThreadSafetyMode.NONE) {
        links[NEXT_LINK]?.let { next ->
            val matcher = PAGE_PATTERN.matcher(next)
            if (!matcher.find() || matcher.groupCount() != 1) {
                null
            } else {
                try {
                    Integer.parseInt(matcher.group(1)!!)
                } catch (ex: NumberFormatException) {
                    Logger.w("cannot parse next page from %s", next)
                    null
                }
            }
        }
    }

    companion object {

        private val LINK_PATTERN = Pattern.compile("<([^>]*)>[\\s]*;[\\s]*rel=\"([a-zA-Z0-9]+)\"")
        private val PAGE_PATTERN = Pattern.compile("\\bpage=(\\d+)")
        private const val NEXT_LINK = "next"

        private fun String.extractLinks(): Map<String, String> {
            val links = mutableMapOf<String, String>()
            val matcher = LINK_PATTERN.matcher(this)

            while (matcher.find()) {
                val count = matcher.groupCount()
                if (count == 2) {
                    links[matcher.group(2)!!] = matcher.group(1)!!
                }
            }
            return links
        }

    }
}

data class ApiErrorResponse<T>(val errorMessage: String, val code: Int, val details: String?) : LiveDataResponse<T>()